package com.shangfeng.mvc.servlet;

import com.shangfeng.mvc.annotation.SFAutowired;
import com.shangfeng.mvc.annotation.SFController;
import com.shangfeng.mvc.annotation.SFRequestMapping;
import com.shangfeng.mvc.annotation.SFService;
import com.shangfeng.mvc.utils.Strings;
import com.sun.deploy.net.HttpResponse;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

/**
 * @program: sfmvc
 * @description: 自己的servlet
 * @author: weizj
 * @create: 2018-11-11 22:05
 **/
public class SFDispatcherServlet extends HttpServlet {

    private static final long serialVersionUID = -2837818726153753958L;
    /**
     * 与web.xml中配置的参数相同
     */
    private static final String LOCATION = "contextConfigLocation";
    /**
     * 用于保存配置信息
     */
    private Properties properties = new Properties();
    /**
     * 保存扫描到的类名称
     */
    private List<String> classNames = new ArrayList<String>();
    /**
     * ioc容器,保存了所有我们初始化的JAVA Bean
     */
    private Map<String, Object> ioc = new HashMap<String, Object>();
    /**
     * web映射容器,保存url和方法之间的映射
     */
    private Map<String, Method> hanlderMapping = new HashMap<String, Method>();

    /**
     * 空的构造函数
     */
    public SFDispatcherServlet() {
        super();
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

        //1.加载配置文件
        doLoadConfig(config.getInitParameter(LOCATION));
        //2.扫描所有类
        doScan(properties.getProperty("scanPackage"));
        //3.初始化扫描到的类并保存到IOC容器中
        doInstance();
        //4.实现依赖注入
        doAutowired();
        //5.构造HandlerMapping
        initHandlerMapping();
        //6.等待请求,匹配URL,定位方法,反射调用匹配到的方法
        super.init(config);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write(//
                    "500 EXCEPTION , DETAILS :  \r\n"//
                            + Arrays.toString(e.getStackTrace()).replace("\\[|\\]", "")//
                            .replace(",\\s", "")//
            );
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (hanlderMapping.isEmpty()) {
            return;
        }
        String url = req.getRequestURI();//请求地址
        String contextPath = req.getContextPath();//contextPath

        url = url.replace(contextPath, "").replace("/+", "/");//转化为之前配置的url地址

        //没有这个url也就是404
        if (!hanlderMapping.containsKey(url)) {
            resp.getWriter().write("404 NOT FOUND");
            return;
        }


        Method method = hanlderMapping.get(url);
        Class<?>[] parameterTypes = method.getParameterTypes();//参数类型
        Map<String, String[]> params = req.getParameterMap();//请求的参数
        Object[] paramValues = new Object[parameterTypes.length];//保存参数的值

        //通过循环构建参数列表
        for (int i = 0; i < parameterTypes.length; i++) {
            Class parameterType = parameterTypes[i];//获取参数名称,进行赋值处理
            if (parameterType == HttpServletRequest.class) {
                paramValues[i] = req;
                continue;
            }
            if (parameterType == HttpResponse.class) {
                paramValues[i] = resp;
                continue;
            }
            if (parameterType == String.class) {
                for (Entry<String, String[]> param : params.entrySet()) {
                    String value = Arrays.toString(param.getValue())//
                            .replace("\\[|\\]", "")//
                            .replace(",\\s", "")//
                            ;
                    paramValues[i] = value;
                }
            }
        }

        //执行方法
        String beanName = Strings.lowerFirstCase(method.getDeclaringClass().getSimpleName());
        try {
            method.invoke(ioc.get(beanName), paramValues);
        } catch (IllegalAccessException e) {
            System.err.println("反射执行方法时发生异常!");
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            System.err.println("反射执行方法时发生异常!");
            e.printStackTrace();
        }


    }

    /**
     * 加载配置文件
     *
     * @param location 配置文件所在位置
     */
    private void doLoadConfig(String location) {
        if (Strings.isNullOrEmpty(location)) {
            throw new RuntimeException("参数错误,无法进行DI");
        }
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(location);
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 扫描所有配置的类
     *
     * @param packageName 包名或类名
     */
    private void doScan(String packageName) {
        if (Strings.isNullOrEmpty(packageName)) {
            throw new RuntimeException("参数错误,无法进行DI");
        }
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replace("\\", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                doScan(packageName + "." + file.getName());
            } else {
                classNames.add(packageName + "." + file.getName().replace(".class", "").trim());
            }
        }
    }

    /**
     * 实例化所有的类并保存到IOC中
     */
    private void doInstance() {
        if (classNames.size() == 0) {
            return;
        }

        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);

                //如果有SFController注解
                if (clazz.isAnnotationPresent(SFController.class)) {
                    String beanName = Strings.lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, clazz.newInstance());
                    continue;
                }

                //如果是有SFService注解
                if (clazz.isAnnotationPresent(SFService.class)) {
                    SFService sfService = clazz.getAnnotation(SFService.class);
                    String beanName = sfService.value();

                    //没有设置名称,使用它的接口类作为beanName
                    if (!Strings.isNullOrEmpty(beanName)) {
                        Class<?>[] interfaces = clazz.getInterfaces();
                        for (Class<?> interfaceClass : interfaces) {
                            ioc.put(interfaceClass.getName(), clazz.newInstance());
                        }
                        continue;
                    }

                    //如果设置了名字,用设置的名字
                    ioc.put(beanName, clazz.newInstance());
                    continue;
                }

                System.out.println(clazz.getName() + "   没有任何注解");

            } catch (ClassNotFoundException e) {
                System.err.println("生成clazz对象时出现异常");
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                System.err.println("实例化java bean时出现异常");
                e.printStackTrace();
            } catch (InstantiationException e) {
                System.err.println("实例化java bean时出现异常");
                e.printStackTrace();
            }
        }
    }

    /**
     * 实现DI
     */
    private void doAutowired() {

        if (ioc.isEmpty()) {
            return;
        }

        for (Entry<String, Object> entry : ioc.entrySet()) {

            Field[] fields = entry.getValue().getClass().getFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(SFAutowired.class)) {
                    continue;
                }

                SFAutowired sfAutowired = field.getAnnotation(SFAutowired.class);
                String beanName = sfAutowired.value().trim();//配置了名称,用配置的名称
                if (Strings.isNullOrEmpty(beanName)) {
                    //没有配置的话,采用注入的接口的名称
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);//如果是private的需要设置这个为true,这里暴力的让他直接为true
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    System.err.println("为  " + field.getName() + "  进行自动注入时发生异常!");
                    e.printStackTrace();
                    continue;
                }
            }

        }

    }

    /**
     * 实现URL和Controller方法之间的映射
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            //不是Controller的话,不做操作
            if (clazz.isAnnotationPresent(SFController.class)) {
                continue;
            }

            String baseUrl = "";

            //如果Controller上配置了SFRequestMapping的话,需要取出类上的地址
            if (clazz.isAnnotationPresent(SFRequestMapping.class)) {
                SFRequestMapping requestMapping = clazz.getAnnotation(SFRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //下面获取每个包含SFReqMapping方法的地址
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {

                //没有注解的,不做操作
                if (!method.isAnnotationPresent(SFRequestMapping.class)) {
                    continue;
                }
                SFRequestMapping requestMapping = method.getAnnotation(SFRequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replace("/+", "/");//这里把多个/替换为一个,提高兼容
                hanlderMapping.put(url, method);
                System.out.println("mapping url : [ " + url + " ] to method : [ " + method.getName() + " ]");

            }
        }
    }


}
